package de.recipeminer.tools;

import com.google.common.base.Optional;
import com.google.common.collect.Sets;
import de.recipeminer.environments.ExecutionContextProvider;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.*;

public class Tools {
    private static Properties mavenPropertiesCache;
    public static final String MAVEN_PROPERTIES = "maven.properties";

    public static String getRubySourcePath() {
        return getMavenRubyPath("maven.sourceDir");
    }

    public static String getRubyTestSourcePath() {
        return getMavenRubyPath("maven.testSourceDir");
    }

    protected static String getMavenRubyPath(String propKey) {
        Properties mavenProps = Tools.getMavenProperties();
        String rubySourcePath = mavenProps.getProperty(propKey).replaceFirst("java$", "ruby");
        File rubySource = new File(rubySourcePath);
        if (rubySource == null || !rubySource.exists()) {
            throw new RuntimeException("Unable to find ruby source path!");
        }
        return rubySource.getAbsolutePath();
    }

    /**
     * Decodes chefkoch json string from charset coding ISO-8859-1 to UTF-8, as given in Config Class
     * performs some other preprocessing tasks using regular expressions.
     *
     * @return transcoded String
     */
    public static String transcodeChefkochString(String encodedString) {
        //decode given string handling it as a ISO-8859-1 encoded string.
        /*MA: removed code for removal of non-alphabetic characters and white-space normalisation. Parsing of
              JSON data and reading out numerik values cannot work if these are removed before the parsing process.
              These steps are sound for preocessing the preparation text string, but should only be applied there
              and implemented in another place*/
        return decodeStringFromCharset(encodedString, Charset.forName("ISO-8859-1"));
    }

    /**
     * determines what Charset Decoding can be used to decipher a text, given by a sample.
     *
     * @param malformedString like "Oliven\u0123l"
     *                        what_it_should_like like "Olivenöl"
     * @return the correct charset
     * @throws UnknownFormatConversionException
     *
     * @throws if no correct decoding could be performed with the charsets supported by this jdk
     * @author Georg Mühlenberg
     */
    public static Charset determineProperCharsetBySample(String malformedString, String expectedString)
            throws UnknownFormatConversionException {
        String decodedString = "";
        Charset current_charset;

//all the keys of supported charsets are stored in an implicit map that Iterator li accesses on.
        Iterator li = Charset.availableCharsets().keySet().iterator();

//charset-WHILE
        while (li.hasNext()) {
            String charsetname = (String) li.next();
            current_charset = Charset.forName(charsetname);
            @SuppressWarnings("UnusedAssignment")
            ByteBuffer bbuf = ByteBuffer.allocate(malformedString.getBytes().length);
            bbuf = de.recipeminer.tools.Tools.stringToByteBuffer(malformedString);

            decodedString = current_charset.decode(bbuf).toString();

            if (decodedString.equals(expectedString)) {
                return current_charset;
            }

        }
//END of charset-test-WHILE

        throw new UnknownFormatConversionException("Proper Charset Could not be found.");
    }

    /**
     * Helping method to support implementation of other resources.
     *
     * @param string  String to decode
     * @param charset Charset to use (e.g. Charset "ISO-8859-1" is returned by Charset.forname("ISO-8859-1") - statement.
     *                See Charset API for detailed information.
     * @return decoded String
     */
    public static String decodeStringFromCharset(String string, Charset charset) {
        //decode given string using the convenience method as implemented in Charset
        return charset.decode(stringToByteBuffer(string)).toString();
    }

    /**
     * Helping Method. Puts String into ByteBuffer
     */
    public static ByteBuffer stringToByteBuffer(String string) {
        byte[] bytes = string.getBytes();
        ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
        return (ByteBuffer) buffer.put(bytes).compact();
    }

    public static boolean executedFromJar() {
        ProtectionDomain pDomain = Tools.class.getProtectionDomain();
        CodeSource cSource = pDomain.getCodeSource();
        String path = cSource.getLocation().getPath();
        return path.endsWith(".jar");
    }

    public static List<String> splitStringToList(String values,  String seperatorPattern) {
        String[] valueArray = values.split(seperatorPattern);
        return Arrays.asList(valueArray);
    }

    public static Set<String> splitStringToSet(String values,  String seperatorPattern) {
        Set<String> valueSet = Sets.newHashSet();
        valueSet.addAll(splitStringToList(values, seperatorPattern));
        return valueSet;
    }
    
    
    public static String generifyFilePath(String path) {
        return path.replace("/", File.separator);
        
    }

    /**
     * Helping Method. Gets String out of ByteBuffer
     */
    public static String byteBufferToString(ByteBuffer buffer) {
        if (buffer.hasArray()) {
            return new String(buffer.array());
        }
        return null;
    }

    public static Optional<Object> getPrivateField(Object obj, String fieldName) {
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            return Optional.fromNullable(field.get(obj));
        } catch (IllegalAccessException e) {
            return null;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

    public static boolean setPrivateField(Object obj, String fieldName, Class valClass, Object valObject) {
        try {

            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, valClass.cast(valObject));
        } catch (IllegalAccessException e) {
            return false;
        } catch (NoSuchFieldException e) {
            return false;
        }
        return true;
    }

    /**
     * Returns a {@link java.util.Properties}-object providing access to various properties with information about
     * the Maven build environment of this program (predominantly varipous pathnames)
     *
     * @return {@code Properties} of the build environment (although mutable, entries should not be changed)
     */
    public static Properties getMavenProperties() {
        if(executedFromJar()) {
            throw new IllegalStateException("Maven properties are not available when run from a jar.");
        }

        InputStream stream = ExecutionContextProvider.currentResourceFinder().getStream(MAVEN_PROPERTIES);
        if (mavenPropertiesCache == null) {
            try {
                mavenPropertiesCache = new Properties();
                mavenPropertiesCache.load(stream);
            } catch (IOException e) {
                String msg = String.format("An error occurred attempting to open %s:", MAVEN_PROPERTIES);
                throw new RuntimeException(msg, e);
            }
        }
        return mavenPropertiesCache;
    }
}
